
Adélia Cruz
Neural Network Developer

En la extracción de datos web, Maxun está ganando atención como una plataforma de código abierto y sin código que simplifica cómo los equipos recopilan datos de la web. Sus flujos de trabajo basados en robots y SDK permiten a desarrolladores y usuarios no técnicos crear y mantener pipelines de raspado sin esfuerzo de ingeniería.
Eso dicho, muchos sitios web del mundo real están protegidos por CAPTCHAs, que a menudo se convierten en el principal cuello de botella durante la extracción de datos. CapSolver funciona bien junto con Maxun al manejar estos desafíos a nivel de infraestructura. Con CapSolver en su lugar, los robots de Maxun pueden continuar operando en páginas protegidas por CAPTCHA de manera más confiable, combinando facilidad de uso con capacidades prácticas de raspado listas para producción.
Maxun es una plataforma de extracción de datos web de código abierto y sin código que permite a los usuarios entrenar robots para raspar sitios web sin escribir código. Cuenta con un constructor visual de robots, un SDK potente para control programático y admite implementaciones en la nube y autohospedadas.
| Clase | Descripción |
|---|---|
| Extract | Crear flujos de trabajo de extracción de datos estructurados con LLM o selectores CSS |
| Scrape | Convertir páginas web en Markdown, HTML o capturas de pantalla limpias |
| Crawl | Descubrir y raspar múltiples páginas automáticamente usando sitemaps y enlaces |
| Search | Realizar búsquedas en la web y extraer contenido de los resultados (DuckDuckGo) |
Maxun puentes el vacío entre la simplicidad sin código y la flexibilidad del desarrollador:
CapSolver es un servicio líder para resolver CAPTCHAs que proporciona soluciones basadas en inteligencia artificial para superar diversos desafíos de CAPTCHA. Con soporte para múltiples tipos de CAPTCHA y tiempos de respuesta rápidos, CapSolver se integra sin problemas en flujos de trabajo automatizados.
CapSolver ayuda a los flujos de trabajo de automatización a manejar la mayoría de los CAPTCHAs y desafíos de verificación comunes durante la extracción de datos y la automatización del navegador, incluyendo:
Al crear robots de Maxun que interactúan con sitios web protegidos—ya sea para extracción de datos, monitoreo de precios o investigación de mercado—los desafíos de CAPTCHA se convierten en un obstáculo significativo. Estos son los motivos por los que la integración es importante:

# Instalar SDK de Maxun
npm install maxun-sdk
# Instalar dependencias adicionales para la integración con CapSolver
npm install axios
# Clonar el repositorio de Maxun
git clone https://github.com/getmaxun/maxun.git
cd maxun
# Iniciar con Docker Compose
docker-compose up -d
Crear un archivo .env con su configuración:
CAPSOLVER_API_KEY=your_capsolver_api_key
MAXUN_API_KEY=your_maxun_api_key
# Para Maxun Cloud (app.maxun.dev)
MAXUN_BASE_URL=https://app.maxun.dev/api/sdk
# Para Maxun autohospedado (por defecto)
# MAXUN_BASE_URL=http://localhost:8080/api/sdk
Nota: La configuración
baseUrles requerida al usar Maxun Cloud. Las instalaciones autohospedadas usan por defectohttp://localhost:8080/api/sdk.
Aquí hay un servicio TypeScript reutilizable que integra CapSolver con Maxun:
import axios, { AxiosInstance } from 'axios';
interface TaskResult {
gRecaptchaResponse?: string;
token?: string;
cookies?: Array<{ name: string; value: string }>;
userAgent?: string;
}
interface CapSolverConfig {
apiKey: string;
timeout?: number;
maxAttempts?: number;
}
class CapSolverService {
private client: AxiosInstance;
private apiKey: string;
private maxAttempts: number;
constructor(config: CapSolverConfig) {
this.apiKey = config.apiKey;
this.maxAttempts = config.maxAttempts || 60;
this.client = axios.create({
baseURL: 'https://api.capsolver.com',
timeout: config.timeout || 30000,
headers: { 'Content-Type': 'application/json' },
});
}
private async createTask(taskData: Record<string, unknown>): Promise<string> {
const response = await this.client.post('/createTask', {
clientKey: this.apiKey,
task: taskData,
});
if (response.data.errorId !== 0) {
throw new Error(`Error de CapSolver: ${response.data.errorDescription}`);
}
return response.data.taskId;
}
private async getTaskResult(taskId: string): Promise<TaskResult> {
for (let attempt = 0; attempt < this.maxAttempts; attempt++) {
await this.delay(2000);
const response = await this.client.post('/getTaskResult', {
clientKey: this.apiKey,
taskId,
});
const { status, solution, errorDescription } = response.data;
if (status === 'ready') {
return solution as TaskResult;
}
if (status === 'failed') {
throw new Error(`Tarea fallida: ${errorDescription}`);
}
}
throw new Error('Tiempo de espera agotado esperando la solución del CAPTCHA');
}
private delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async solveReCaptchaV2(websiteUrl: string, websiteKey: string): Promise<string> {
const taskId = await this.createTask({
type: 'ReCaptchaV2TaskProxyLess',
websiteURL: websiteUrl,
websiteKey,
});
const solution = await this.getTaskResult(taskId);
return solution.gRecaptchaResponse || '';
}
async solveReCaptchaV3(
websiteUrl: string,
websiteKey: string,
pageAction: string = 'submit'
): Promise<string> {
const taskId = await this.createTask({
type: 'ReCaptchaV3TaskProxyLess',
websiteURL: websiteUrl,
websiteKey,
pageAction,
});
const solution = await this.getTaskResult(taskId);
return solution.gRecaptchaResponse || '';
}
async solveTurnstile(
websiteUrl: string,
websiteKey: string,
action?: string,
cdata?: string
): Promise<string> {
const taskData: Record<string, unknown> = {
type: 'AntiTurnstileTaskProxyLess',
websiteURL: websiteUrl,
websiteKey,
};
// Añadir metadatos opcionales
if (action || cdata) {
taskData.metadata = {};
if (action) (taskData.metadata as Record<string, string>).action = action;
if (cdata) (taskData.metadata as Record<string, string>).cdata = cdata;
}
const taskId = await this.createTask(taskData);
const solution = await this.getTaskResult(taskId);
return solution.token || '';
}
async checkBalance(): Promise<number> {
const response = await this.client.post('/getBalance', {
clientKey: this.apiKey,
});
return response.data.balance || 0;
}
}
export { CapSolverService, CapSolverConfig, TaskResult };
★ Insight ─────────────────────────────────────
El servicio de CapSolver utiliza un patrón de sondeo (getTaskResult) porque la resolución de CAPTCHA es asincrónica: la API acepta una tarea, la procesa en sus servidores y devuelve un resultado cuando está listo. El retraso de 2 segundos entre sondeos equilibra la respuesta con los límites de tasa de la API.
─────────────────────────────────────────────────
Como Maxun opera a un nivel más alto que la automatización del navegador en bruto, el enfoque de integración se centra en resolver CAPTCHAs antes o durante la ejecución del robot:
import { Extract } from 'maxun-sdk';
import { CapSolverService } from './capsolver-service';
const CAPSOLVER_API_KEY = process.env.CAPSOLVER_API_KEY!;
const MAXUN_API_KEY = process.env.MAXUN_API_KEY!;
const MAXUN_BASE_URL = process.env.MAXUN_BASE_URL || 'https://app.maxun.dev/api/sdk';
const capSolver = new CapSolverService({ apiKey: CAPSOLVER_API_KEY });
const extractor = new Extract({
apiKey: MAXUN_API_KEY,
baseUrl: MAXUN_BASE_URL,
});
async function extractWithRecaptchaV2() {
const targetUrl = 'https://example.com/protected-page';
const recaptchaSiteKey = '6LcxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxABC';
console.log('Resolviendo reCAPTCHA v2...');
// Resolver el CAPTCHA primero
const token = await capSolver.solveReCaptchaV2(targetUrl, recaptchaSiteKey);
console.log('CAPTCHA resuelto, creando robot de extracción...');
// Crear un robot usando encadenamiento de métodos
const robot = await extractor
.create('Extractor de Productos')
.navigate(targetUrl)
.type('#g-recaptcha-response', token)
.click('button[type="submit"]')
.wait(2000)
.captureList({ selector: '.product-item' });
// Ejecutar el robot
const result = await robot.run({ timeout: 30000 });
console.log('Extracción completa:', result.data);
return result.data;
}
extractWithRecaptchaV2().catch(console.error);
import { Extract } from 'maxun-sdk';
import { CapSolverService } from './capsolver-service';
const CAPSOLVER_API_KEY = process.env.CAPSOLVER_API_KEY!;
const MAXUN_API_KEY = process.env.MAXUN_API_KEY!;
const MAXUN_BASE_URL = process.env.MAXUN_BASE_URL || 'https://app.maxun.dev/api/sdk';
const capSolver = new CapSolverService({ apiKey: CAPSOLVER_API_KEY });
const extractor = new Extract({
apiKey: MAXUN_API_KEY,
baseUrl: MAXUN_BASE_URL,
});
async function extractWithRecaptchaV3() {
const targetUrl = 'https://example.com/v3-protected';
const recaptchaSiteKey = '6LcxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxDEF';
console.log('Resolviendo reCAPTCHA v3 con alto puntaje...');
// Resolver con acción de página personalizada
const token = await capSolver.solveReCaptchaV3(
targetUrl,
recaptchaSiteKey,
'submit' // pageAction
);
console.log('Token obtenido con alto puntaje, creando robot...');
// Crear robot de extracción usando encadenamiento de métodos
const robot = await extractor
.create('Extractor protegido por v3')
.navigate(targetUrl)
.type('input[name="g-recaptcha-response"]', token)
.click('#submit-btn')
.wait(2000)
.captureText({ resultData: '.result-data' });
const result = await robot.run({ timeout: 30000 });
console.log('Datos extraídos:', result.data);
return result.data;
}
extractWithRecaptchaV3().catch(console.error);
import { Scrape } from 'maxun-sdk';
import { CapSolverService } from './capsolver-service';
const CAPSOLVER_API_KEY = process.env.CAPSOLVER_API_KEY!;
const MAXUN_API_KEY = process.env.MAXUN_API_KEY!;
const MAXUN_BASE_URL = process.env.MAXUN_BASE_URL || 'https://app.maxun.dev/api/sdk';
const capSolver = new CapSolverService({ apiKey: process.env.CAPSOLVER_API_KEY! });
const scraper = new Scrape({
apiKey: MAXUN_API_KEY,
baseUrl: MAXUN_BASE_URL,
});
async function extractWithTurnstile() {
const targetUrl = 'https://example.com/turnstile-protected';
const turnstileSiteKey = '0x4xxxxxxxxxxxxxxxxxxxxxxxxxxxxGHI';
console.log('Resolviendo Cloudflare Turnstile...');
// Resolver con metadatos opcionales (acción y cdata)
const token = await capSolver.solveTurnstile(
targetUrl,
turnstileSiteKey,
'login', // acción opcional
'0000-1111-2222-3333-ejemplo-cdata' // cdata opcional
);
console.log('Turnstile resuelto, creando robot de raspado...');
// Crear robot de raspado - para Turnstile, normalmente necesitamos
// enviar el token mediante una solicitud POST primero, luego raspar
const robot = await scraper.create('raspador-de-turnstile', targetUrl, {
formats: ['markdown', 'html'],
});
const result = await robot.run({ timeout: 30000 });
console.log('Extracción completa');
console.log('Markdown:', result.data.markdown?.substring(0, 500));
return result.data;
}
extractWithTurnstile().catch(console.error);
La clase Extract se utiliza para extraer datos estructurados de elementos específicos de una página. Soporta extracción impulsada por LLM (usando prompts de lenguaje natural) y extracción no LLM (usando selectores CSS):
import { Extract } from 'maxun-sdk';
import { CapSolverService } from './capsolver-service';
const capSolver = new CapSolverService({ apiKey: process.env.CAPSOLVER_API_KEY! });
const extractor = new Extract({
apiKey: process.env.MAXUN_API_KEY!,
baseUrl: process.env.MAXUN_BASE_URL || 'https://app.maxun.dev/api/sdk',
});
interface ProductData {
name: string;
price: string;
rating: string;
}
async function extractProductsWithCaptcha(): Promise<ProductData[]> {
const targetUrl = 'https://example.com/products';
const siteKey = '6LcxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxABC';
// Resolver CAPTCHA previamente
const captchaToken = await capSolver.solveReCaptchaV2(targetUrl, siteKey);
console.log('CAPTCHA resuelto, creando robot de extracción...');
// Crear robot de extracción usando encadenamiento de métodos
const robot = await extractor
.create('Extractor de Productos')
.navegar(targetUrl)
.escribir('#g-recaptcha-response', captchaToken)
.hacerClic('button[type="submit"]')
.esperar(3000)
.capturarLista({
selector: '.product-card',
paginación: { tipo: 'hacerClicSiguiente', selector: '.next-page' },
maxItems: 50,
});
// Ejecutar la extracción
const resultado = await robot.ejecutar({ timeout: 60000 });
return resultado.data.listaDatos as ProductData[];
}
extraerProductosConCaptcha()
.then((productos) => {
productos.forEach((producto) => {
console.log(`${producto.nombre}: ${producto.precio}`);
});
})
.catch(console.error);
★ Insight ─────────────────────────────────────
El método capturarLista en la clase Extract de Maxun detecta automáticamente los campos dentro de los elementos de lista y maneja la paginación. Cuando especifica un tipo de paginación (hacerClicSiguiente, hacerClicCargarMas), el robot continuará extrayendo hasta alcanzar su límite o agotar las páginas.
─────────────────────────────────────────────────
La clase Scrape convierte páginas web en HTML limpio, Markdown listo para LLM o capturas de pantalla:
import { Scrape } from 'maxun-sdk';
import { CapSolverService } from './capsolver-service';
import axios from 'axios';
const capSolver = new CapSolverService({ apiKey: process.env.CAPSOLVER_API_KEY! });
const scraper = new Scrape({
apiKey: process.env.MAXUN_API_KEY!,
baseUrl: process.env.MAXUN_BASE_URL || 'https://app.maxun.dev/api/sdk',
});
interface ResultadoScrape {
url: string;
markdown?: string;
html?: string;
captchaResuelto: boolean;
}
async function scrapeConManejoCaptcha(): Promise<ResultadoScrape[]> {
const urls = [
'https://example.com/pagina1',
'https://example.com/pagina2',
'https://example.com/pagina3',
];
const resultados: ResultadoScrape[] = [];
for (const url of urls) {
try {
// Para páginas protegidas por CAPTCHA, resolver primero y establecer sesión
const siteKey = '6LcxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxABC'; // Obtener dinámicamente si es necesario
console.log(`Resolviendo CAPTCHA para ${url}...`);
const tokenCaptcha = await capSolver.solveReCaptchaV2(url, siteKey);
// Enviar CAPTCHA para obtener cookies de sesión
const respuestaVerificar = await axios.post(`${url}/verificar`, {
'g-recaptcha-response': tokenCaptcha,
});
// Crear robot de scrapeo para la página autenticada
const robot = await scraper.crear(`escrapador-${Date.now()}`, url, {
formatos: ['markdown', 'html'],
});
const resultado = await robot.ejecutar({ timeout: 30000 });
resultados.push({
url,
markdown: resultado.data.markdown,
html: resultado.data.html,
captchaResuelto: true,
});
// Limpiar - eliminar robot después de usarlo
await robot.eliminar();
} catch (error) {
console.error(`Fallo al escrapear ${url}:`, error);
resultados.push({ url, captchaResuelto: false });
}
}
return resultados;
}
scrapeConManejoCaptcha().then(console.log).catch(console.error);
La clase Crawl descubre y escrapea múltiples páginas usando sitemaps y seguimiento de enlaces:
import { Crawl } from 'maxun-sdk';
import { CapSolverService } from './capsolver-service';
import axios from 'axios';
const capSolver = new CapSolverService({ apiKey: process.env.CAPSOLVER_API_KEY! });
const crawler = new Crawl({
apiKey: process.env.MAXUN_API_KEY!,
baseUrl: process.env.MAXUN_BASE_URL || 'https://app.maxun.dev/api/sdk',
});
interface ResultadoPagina {
url: string;
titulo: string;
texto: string;
cantidadPalabras: number;
}
async function crawlearConProtecciónCaptcha(): Promise<ResultadoPagina[]> {
const urlInicio = 'https://example.com';
const siteKey = '6LcxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxABC';
// Resolver CAPTCHA previamente para el dominio
console.log('Resolviendo CAPTCHA para acceso al dominio...');
const tokenCaptcha = await capSolver.solveReCaptchaV2(urlInicio, siteKey);
// Enviar CAPTCHA para establecer sesión (específico del sitio)
await axios.post(`${urlInicio}/verificar`, {
'g-recaptcha-response': tokenCaptcha,
});
// Crear robot de crawleo con configuración de alcance de dominio
const robot = await crawler.crear('crawleo-del-sitio', urlInicio, {
modo: 'dominio', // Alcance de crawleo: 'dominio', 'subdominio' o 'ruta'
límite: 50, // Máximo de páginas a crawlear
profundidadMáxima: 3, // Qué tan profundo seguir enlaces
usarSitemap: true, // Analizar sitemap.xml para URLs
seguirEnlaces: true, // Extraer y seguir enlaces de página
incluirRutas: ['/blog/', '/docs/'], // Patrones regex a incluir
excluirRutas: ['/admin/', '/login/'], // Patrones regex a excluir
respetarRobots: true, // Honrar robots.txt
});
// Ejecutar el crawleo
const resultado = await robot.ejecutar({ timeout: 120000 });
// Cada página en resultado contiene: metadata, html, texto, cantidadPalabras, enlaces
return resultado.data.crawlData.map((pagina: any) => ({
url: pagina.metadata.url,
titulo: pagina.metadata.titulo,
texto: pagina.texto,
cantidadPalabras: pagina.cantidadPalabras,
}));
}
crawlearConProtecciónCaptcha()
.then((páginas) => {
console.log(`Crawleadas ${páginas.length} páginas`);
páginas.forEach((página) => {
console.log(`- ${página.titulo}: ${página.url} (${página.cantidadPalabras} palabras)`);
});
})
.catch(console.error);
Para sitios que requieren CAPTCHA antes de acceder al contenido, use un flujo de trabajo de autenticación previa:
import axios from 'axios';
import { Extract } from 'maxun-sdk';
import { CapSolverService } from './capsolver-service';
const capSolver = new CapSolverService({ apiKey: process.env.CAPSOLVER_API_KEY! });
const extractor = new Extract({
apiKey: process.env.MAXUN_API_KEY!,
baseUrl: process.env.MAXUN_BASE_URL || 'https://app.maxun.dev/api/sdk',
});
interface CookiesSesión {
nombre: string;
valor: string;
dominio: string;
}
async function autenticarPrevioConCaptcha(
urlLogin: string,
siteKey: string
): Promise<CookiesSesión[]> {
// Paso 1: Resolver el CAPTCHA
const tokenCaptcha = await capSolver.solveReCaptchaV2(urlLogin, siteKey);
// Paso 2: Enviar el token del CAPTCHA para obtener cookies de sesión
const respuesta = await axios.post(
urlLogin,
{
'g-recaptcha-response': tokenCaptcha,
},
{
withCredentials: true,
maxRedirects: 0,
validateStatus: (estado) => estado < 400,
}
);
// Paso 3: Extraer cookies de la respuesta
const cookiesSet = respuesta.headers['set-cookie'] || [];
const cookies: CookiesSesión[] = cookiesSet.map((cookie: string) => {
const [nombreValor] = cookie.split(';');
const [nombre, valor] = nombreValor.split('=');
return {
nombre: nombre.trim(),
valor: valor.trim(),
dominio: new URL(urlLogin).hostname,
};
});
return cookies;
}
async function extraerConAutenticaciónPrevia() {
const urlLogin = 'https://example.com/verificar';
const urlObjetivo = 'https://example.com/datos-privados';
const siteKey = '6LcxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxABC';
// Autenticar previamente para obtener cookies de sesión
const cookiesSesión = await autenticarPrevioConCaptcha(urlLogin, siteKey);
console.log('Sesión establecida, creando robot de extracción...');
// Crear robot de extracción usando encadenamiento de métodos
// Nota: Usar setCookies() para pasar cookies de sesión autenticadas
const robot = await extractor
.crear('Extractor Autenticado')
.setCookies(cookiesSesión)
.navegar(urlObjetivo)
.esperar(2000)
.capturarTexto({ contenido: '.contenido-privado' });
// Ejecutar la extracción
const resultado = await robot.ejecutar({ timeout: 30000 });
return resultado.data;
}
extraerConAutenticaciónPrevia().then(console.log).catch(console.error);
★ Insight ─────────────────────────────────────
El patrón de autenticación previa separa la resolución de CAPTCHA de la extracción de datos. Esto es especialmente útil para Maxun porque opera a un nivel de abstracción más alto: en lugar de inyectar tokens en el DOM, establece primero una sesión autenticada, luego deja que los robots de Maxun trabajen dentro de esa sesión.
─────────────────────────────────────────────────
Manejar CAPTCHAs en múltiples ejecuciones concurrentes de robots:
import { Scrape } from 'maxun-sdk';
import { CapSolverService } from './capsolver-service';
const capSolver = new CapSolverService({ apiKey: process.env.CAPSOLVER_API_KEY! });
const scraper = new Scrape({
apiKey: process.env.MAXUN_API_KEY!,
baseUrl: process.env.MAXUN_BASE_URL || 'https://app.maxun.dev/api/sdk',
});
interface TareaExtracción {
url: string;
siteKey?: string;
}
interface ResultadoExtracción {
url: string;
datos: { markdown?: string; html?: string };
captchaResuelto: boolean;
duración: number;
}
async function procesarTarea(tarea: TareaExtracción): Promise<ResultadoExtracción> {
const tiempoInicio = Date.now();
let captchaResuelto = false;
// Resolver CAPTCHA si se proporciona siteKey
if (tarea.siteKey) {
console.log(`Resolviendo CAPTCHA para ${tarea.url}...`);
const token = await capSolver.solveReCaptchaV2(tarea.url, tarea.siteKey);
captchaResuelto = true;
// El token se enviaría para establecer sesión antes de escrapear
}
// Crear y ejecutar un robot de escrapeo
const robot = await scraper.crear(`escrapador-${Date.now()}`, tarea.url, {
formatos: ['markdown', 'html'],
});
const resultado = await robot.ejecutar({ timeout: 30000 });
// Limpiar robot después de usarlo
await robot.eliminar();
return {
url: tarea.url,
datos: {
markdown: resultado.data.markdown,
html: resultado.data.html,
},
captchaResuelto,
duración: Date.now() - tiempoInicio,
};
}
async function ejecutarExtraccionesParalelas(
tareas: TareaExtracción[],
concurrencia: number = 5
): Promise<ResultadoExtracción[]> {
const resultados: ResultadoExtracción[] = [];
const fragmentos: TareaExtracción[][] = [];
// Dividir tareas en fragmentos para controlar la concurrencia
for (let i = 0; i < tareas.length; i += concurrencia) {
fragmentos.push(tareas.slice(i, i + concurrencia));
}
for (const fragmento of fragmentos) {
const resultadosFragmento = await Promise.all(fragmento.map(procesarTarea));
resultados.push(...resultadosFragmento);
}
return resultados;
}
// Ejemplo de uso
const tareas: TareaExtracción[] = [
{ url: 'https://sitio1.com/datos', siteKey: '6Lc...' },
{ url: 'https://sitio2.com/datos' },
{ url: 'https://sitio3.com/datos', siteKey: '6Lc...' },
// ... más tareas
];
ejecutarExtraccionesParalelas(tareas, 5)
.then((resultados) => {
const resueltos = resultados.filter((r) => r.captchaResuelto).length;
console.log(`Completadas ${resultados.length} extracciones, resueltos ${resueltos} CAPTCHAs`);
resultados.forEach((r) => {
console.log(`${r.url}: ${r.duración}ms`);
});
})
.catch(console.error);
async function resolverConReintento<T>(
funciónSolver: () => Promise<T>,
maxReintentos: number = 3
): Promise<T> {
let últimoError: Error | undefined;
for (let intento = 0; intento < maxReintentos; intento++) {
try {
return await funciónSolver();
} catch (error) {
últimoError = error as Error;
console.warn(`Intento ${intento + 1} fallido: ${últimoError.message}`);
// Retraso exponencial
await new Promise((resolver) =>
setTimeout(resolver, Math.pow(2, intento) * 1000)
);
}
}
throw new Error(`Se excedió el número máximo de reintentos: ${últimoError?.message}`);
}
// Uso
const token = await resolverConReintento(() =>
capSolver.solveReCaptchaV2(url, siteKey)
);
async function asegurarBalanceSuficiente(minBalance: number = 1.0): Promise<void> {
const balance = await capSolver.verificarBalance();
if (balance < minBalance) {
throw new Error(
`Balance insuficiente de CapSolver: $${balance.toFixed(2)}. Por favor recargue.`
);
}
console.log(`Balance de CapSolver: $${balance.toFixed(2)}`);
}
// Verificar balance antes de iniciar tareas de extracción
await asegurarBalanceSuficiente(5.0);
interface TokenCaché {
token: string;
timestamp: number;
}
class AlmacenamientoCaché {
private cache = new Map<string, TokenCaché>();
private ttlMs: number;
constructor(ttlSegundos: number = 90) {
this.ttlMs = ttlSegundos * 1000;
}
private obtenerClave(dominio: string, siteKey: string): string {
return `${dominio}:${siteKey}`;
}
obtener(dominio: string, siteKey: string): string | null {
const clave = this.obtenerClave(dominio, siteKey);
const caché = this.cache.get(clave);
if (!caché) return null;
if (Date.now() - caché.timestamp > this.ttlMs) {
this.cache.delete(clave);
return null;
}
return caché.token;
}
establecer(dominio: string, siteKey: string, token: string): void {
const clave = this.obtenerClave(dominio, siteKey);
this.cache.set(clave, { token, timestamp: Date.now() });
}
}
const almacenamientoCaché = new AlmacenamientoCaché(90);
async function obtenerTokenCaché(
url: string,
siteKey: string
): Promise<string> {
const dominio = new URL(url).hostname;
const caché = almacenamientoCaché.obtener(dominio, siteKey);
if (caché) {
console.log('Usando token en caché');
return caché;
}
const token = await capSolver.solveReCaptchaV2(url, siteKey);
almacenamientoCaché.establecer(dominio, siteKey, token);
return token;
}
Maxun admite configuración a través de variables de entorno y opciones del SDK:
| Configuración | Descripción | Predeterminado |
|---|---|---|
MAXUN_API_KEY |
Su clave de API de Maxun | - |
MAXUN_BASE_URL |
URL base de la API de Maxun | http://localhost:8080/api/sdk (autohospedado) o https://app.maxun.dev/api/sdk (nube) |
CAPSOLVER_API_KEY |
Su clave de API de CapSolver | - |
CAPSOLVER_TIMEOUT |
Tiempo de espera en ms | 30000 |
CAPSOLVER_MAX_ATTEMPTS |
Máximo de intentos de polling | 60 |
import { Extract, Scrape, Crawl, Search } from 'maxun-sdk';
import { CapSolverService } from './capsolver-service';
// Para Maxun Cloud (app.maxun.dev)
const MAXUN_BASE_URL = 'https://app.maxun.dev/api/sdk';
// Para Maxun autohospedado (predeterminado)
// const MAXUN_BASE_URL = 'http://localhost:8080/api/sdk';
// Configurar módulos del SDK de Maxun
const extractor = new Extract({
apiKey: process.env.MAXUN_API_KEY!,
baseUrl: MAXUN_BASE_URL,
});
const scraper = new Scrape({
apiKey: process.env.MAXUN_API_KEY!,
baseUrl: MAXUN_BASE_URL,
});
const crawler = new Crawl({
apiKey: process.env.MAXUN_API_KEY!,
baseUrl: MAXUN_BASE_URL,
});
const searcher = new Search({
apiKey: process.env.MAXUN_API_KEY!,
baseUrl: MAXUN_BASE_URL,
});
// Configurar CapSolver
const capSolver = new CapSolverService({
Aprende una arquitectura de raspado web escalable en Rust con reqwest, scraper, raspado asíncrono, raspado con navegador sin cabeza, rotación de proxies y manejo de CAPTCHA conforme.

Automatiza la resolución de CAPTCHA con Nanobot y CapSolver. Utiliza Playwright para resolver reCAPTCHA y Cloudflare autónomamente.
